#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/mman.h>

#include <aura.h>
#include <auraplus.h>
#include <linknode.h>

/*
    enum Aura_MemoryProtection {
        Aura_MemoryProtection_Read         = 0x01,
        Aura_MemoryProtection_Write        = 0x02,
        Aura_MemoryProtection_Exec         = 0x04,
        Aura_MemoryProtection_RW           = 0x03,
        Aura_MemoryProtection_RX           = 0x05,
        Aura_MemoryProtection_RWX          = 0x07,
    }

    enum IoRemapFlag {
        IoRemapFlag_Direct = 0x08,
        IoRemapFlag_BufferedWrite = 0x18,
    }

    enum MemoryMap {
        Aura_MemoryMap_Private          = 0x01,
        Aura_MemoryMap_Shared           = 0x02,
        Aura_MemoryMap_Fixed            = 0x04,
        Aura_MemoryMap_Upgrow           = 0x10,
        Aura_MemoryMap_Downgrow         = 0x20,
    }
*/

#define RESERVED_SIZE        0x00010000
#define USER_HEAP_SIZE       0x08000000
#define USER_HEAP_BASE       0x40000000
#define USER_HEAP_LIMIT     (USER_HEAP_BASE  + USER_HEAP_SIZE)
#define VIRTUAL_ALLOC_BASE  (USER_HEAP_LIMIT + RESERVED_SIZE)
#define VIRTUAL_ALLOC_LIMIT (0X60000000 - RESERVED_SIZE)
#define VIRTUAL_ALLOC_SIZE  (VIRTUAL_ALLOC_LIMIT - VIRTUAL_ALLOC_BASE)

static DLinkNode s_dVMemeoryInfoList;
static Aura_Handle s_hVMemeoryInfoLock;

EXTERN_C int AuraMemoryProtectionConvertToLinux(Aura_MemoryProtection protect);


struct VMemeoryInfo : public DLinkNode {
    Aura_Address          m_ulBaseAddr;
    Aura_MemorySize       m_iTotalSize;
    Aura_MemoryProtection m_iProtect;
    Aura_MemoryMapFlags   m_iFlags;

    VMemeoryInfo(Aura_Address base, Aura_MemorySize size, Aura_MemoryProtection portect, Aura_MemoryMapFlags flags) :
        m_ulBaseAddr(base),
        m_iTotalSize(size),
        m_iProtect(portect),
        m_iFlags(flags)
        { }
};

EXTERN_C Aura_ECode InitVirtualMemory()
{
    CreateLock(&s_hVMemeoryInfoLock);
    if(NULL == s_hVMemeoryInfoLock){
        PERROR("Error: Virtual Memory Map failed! \
        file:%s. line:%d. \n",
        __FILE__, __LINE__);
        return AURA_E_OUT_OF_MEMORY;
    }

    //And init the list
    s_dVMemeoryInfoList.Initialize();
    return AURA_NOERROR;
}

EXTERN_C void DeleVirtualMemory()
{
    VMemeoryInfo *pVMemeoryInfo;

    Lock(s_hVMemeoryInfoLock);
    while(s_dVMemeoryInfoList.Next() != &s_dVMemeoryInfoList){
        pVMemeoryInfo = (VMemeoryInfo *)s_dVMemeoryInfoList.Next();
        munmap( (Aura_PVoid)pVMemeoryInfo->m_ulBaseAddr,
            (size_t)pVMemeoryInfo->m_iTotalSize);
        pVMemeoryInfo->Detach();
        delete pVMemeoryInfo;
    }
    Unlock(s_hVMemeoryInfoLock);

    DestroyLock(s_hVMemeoryInfoLock);
}

int AuraMemoryMapFlagsConvertToLinux(Aura_MemoryMapFlags flags)
{
    int iRetFlags = 0;

    //Set default flag
    iRetFlags |= MAP_PRIVATE;

    if(flags & Aura_MemoryMap_Private){
        iRetFlags |= MAP_PRIVATE;
    }

    if(flags & Aura_MemoryMap_Shared){
        iRetFlags |= MAP_SHARED;
    }

    if(flags & Aura_MemoryMap_Fixed){
        iRetFlags |= MAP_FIXED;
    }

    if(flags & Aura_MemoryMap_Downgrow){
        iRetFlags |= MAP_GROWSDOWN;
    }

    return iRetFlags;

}

AURA_API AuraVirtualMemory_Map(
        /* [in] */ Aura_Address vBaseAddr,
        /* [in] */ Aura_MemorySize size,
        /* [in] */ Aura_MemoryMapFlags flags,
        /* [in] */ Aura_MemoryProtection protect,
        /* [out] */ Aura_Address *pBaseAddr)
{
    VMemeoryInfo *pVMemeoryInfo;
    int iFlags;
    int iProt;
    Aura_PVoid pBase;

    assert(IS_PAGE_ALIGNED(vBaseAddr));
    assert(size > 0);
    assert(IS_PAGE_ALIGNED(size));
    assert(NULL != pBaseAddr);
    assert(!((flags & Aura_MemoryMap_Upgrow) && (flags & Aura_MemoryMap_Downgrow)));

    if (size > VIRTUAL_ALLOC_SIZE ) {
        return AURA_E_NOT_ENOUGH_ADDRESS_SPACE;
    }
    if (flags & Aura_MemoryMap_Fixed) {
        if ((vBaseAddr < VIRTUAL_ALLOC_BASE)
            && (vBaseAddr >= VIRTUAL_ALLOC_LIMIT)) {
            return AURA_E_INVALID_ARGUMENT;
        }
        if (vBaseAddr + size > VIRTUAL_ALLOC_LIMIT) {
            return AURA_E_NOT_ENOUGH_ADDRESS_SPACE;
        }
    }

    pVMemeoryInfo = new VMemeoryInfo(vBaseAddr, size, protect, flags);
    if(NULL == pVMemeoryInfo){
        PERROR("Error: Virtual Memory Map failed! \
        file:%s. line:%d. \n",
        __FILE__, __LINE__);
        return AURA_E_OUT_OF_MEMORY;
    }

    iFlags = AuraMemoryMapFlagsConvertToLinux(flags);

    iFlags |= MAP_ANONYMOUS;

    if((Aura_MemoryMap_Downgrow & flags) || (Aura_MemoryMap_Upgrow & flags)){
        iProt = AuraMemoryProtectionConvertToLinux(protect);
    } else {
        iProt = PROT_NONE;
    }

    pBase = mmap((Aura_PVoid)vBaseAddr, (size_t)size, iProt, iFlags, GetFdZero(), 0 );
    if(MAP_FAILED == pBase){
        PERROR("Error: Virtual Memory Map failed! \
            file:%s. line:%d.\n", __FILE__, __LINE__);
        delete(pVMemeoryInfo);
        return AURA_E_INVALID_OPERATION;
    }

    *pBaseAddr = (Aura_Address)pBase;
    pVMemeoryInfo->m_ulBaseAddr = (Aura_Address)pBase;

    Lock(s_hVMemeoryInfoLock);
    s_dVMemeoryInfoList.InsertNext(pVMemeoryInfo);
    Unlock(s_hVMemeoryInfoLock);

    return AURA_NOERROR;
}

AURA_API AuraVirtualMemory_Query(
        /* [in] */ Aura_Address vAddress,
        /* [out] */ Aura_Address *pBaseAddr,
        /* [out] */ Aura_MemorySize *pSize)
{
    VMemeoryInfo *pVMemeoryInfo;

    assert(IS_PAGE_ALIGNED(vAddress));
    assert(NULL != pBaseAddr);
    assert(NULL != pSize);

    Lock(s_hVMemeoryInfoLock);
    ForEachDLinkNode(VMemeoryInfo *, pVMemeoryInfo, &s_dVMemeoryInfoList) {
        if(Aura_MemoryMap_Downgrow & pVMemeoryInfo->m_iFlags){
            if((vAddress <= pVMemeoryInfo->m_ulBaseAddr)
                && (vAddress >= pVMemeoryInfo->m_ulBaseAddr - pVMemeoryInfo->m_iTotalSize)){
                *pBaseAddr = pVMemeoryInfo->m_ulBaseAddr;
                *pSize = pVMemeoryInfo->m_iTotalSize;
                Unlock(s_hVMemeoryInfoLock);
                return AURA_NOERROR;
            }
        } else {
            if((vAddress >= pVMemeoryInfo->m_ulBaseAddr)
                && (vAddress <= pVMemeoryInfo->m_ulBaseAddr + pVMemeoryInfo->m_iTotalSize)){
                *pBaseAddr = pVMemeoryInfo->m_ulBaseAddr;
                *pSize = pVMemeoryInfo->m_iTotalSize;
                Unlock(s_hVMemeoryInfoLock);
                return AURA_NOERROR;
            }
        }
    }
    Unlock(s_hVMemeoryInfoLock);

    return AURA_E_DOES_NOT_EXIST;
}

AURA_API AuraVirtualMemory_SetProtection(
        /* [in] */ Aura_Address vBaseAddr,
        /* [in] */ Aura_MemorySize size,
        /* [in] */ Aura_MemoryProtection protect)
{
    VMemeoryInfo *pVMemeoryInfo;

    Lock(s_hVMemeoryInfoLock);
    ForEachDLinkNode(VMemeoryInfo *, pVMemeoryInfo, &s_dVMemeoryInfoList) {
        if((vBaseAddr == pVMemeoryInfo->m_ulBaseAddr)
            && (size == pVMemeoryInfo->m_iTotalSize)){
            pVMemeoryInfo->m_iProtect = protect;
            Unlock(s_hVMemeoryInfoLock);
            return AURA_NOERROR;
        }
    }
    Unlock(s_hVMemeoryInfoLock);

    return AURA_E_INVALID_ARGUMENT;
}

AURA_API AuraVirtualMemory_CheckProtection(
        /* [in] */ Aura_Address vAddress,
        /* [in] */ Aura_MemoryProtection protect,
        /* [out] */ Aura_Bool *pIsValid)
{
    VMemeoryInfo *pVMemeoryInfo;

    assert(NULL != pIsValid);

    Lock(s_hVMemeoryInfoLock);
    ForEachDLinkNode(VMemeoryInfo *, pVMemeoryInfo, &s_dVMemeoryInfoList) {
        if(Aura_MemoryMap_Downgrow & pVMemeoryInfo->m_iFlags){
            if((vAddress <= pVMemeoryInfo->m_ulBaseAddr)
                && (vAddress >= pVMemeoryInfo->m_ulBaseAddr - pVMemeoryInfo->m_iTotalSize)){
                if (protect == pVMemeoryInfo->m_iProtect) {
                    *pIsValid = AURA_TRUE;
                }
                else {
                    *pIsValid = AURA_FALSE;
                }
                Unlock(s_hVMemeoryInfoLock);
                return AURA_NOERROR;
            }
        } else {
            if((vAddress >= pVMemeoryInfo->m_ulBaseAddr)
                && (vAddress <= pVMemeoryInfo->m_ulBaseAddr + pVMemeoryInfo->m_iTotalSize)){
                if (protect == pVMemeoryInfo->m_iProtect) {
                    *pIsValid = AURA_TRUE;
                }
                else {
                    *pIsValid = AURA_FALSE;
                }
                Unlock(s_hVMemeoryInfoLock);
                return AURA_NOERROR;
            }
        }
    }
    Unlock(s_hVMemeoryInfoLock);

    return AURA_E_ACCESS_DENIED;
}

Aura_ECode CommitAddress(VMemeoryInfo *pVMemeoryInfo, Aura_Address vAddress)
{
    int iFlags, iProt;
    Aura_PVoid pBase;
    size_t size;

    iFlags = AuraMemoryMapFlagsConvertToLinux(pVMemeoryInfo->m_iFlags);
    iProt  = AuraMemoryProtectionConvertToLinux(pVMemeoryInfo->m_iProtect);

    iFlags |= MAP_ANONYMOUS;

    if(Aura_MemoryMap_Upgrow & pVMemeoryInfo->m_iFlags){

        size = vAddress - pVMemeoryInfo->m_ulBaseAddr;
        pBase = mmap( (Aura_PVoid)pVMemeoryInfo->m_ulBaseAddr, size, iProt, iFlags, GetFdZero(), 0 );
        if(MAP_FAILED == pBase){
            PERROR("Error: Virtual Memory Commit failed! \
                file:%s. line:%d.\n", __FILE__, __LINE__);
            delete(pVMemeoryInfo);
            return AURA_E_OUT_OF_MEMORY;
        }

    } else if(Aura_MemoryMap_Downgrow & pVMemeoryInfo->m_iFlags){

        size = pVMemeoryInfo->m_ulBaseAddr - vAddress;
        pBase = mmap( (Aura_PVoid)pVMemeoryInfo->m_ulBaseAddr, size, iProt, iFlags, GetFdZero(), 0 );
        if(MAP_FAILED == pBase){
            PERROR("Error: Virtual Memory Commit failed! \
                file:%s. line:%d.\n", __FILE__, __LINE__);
            delete(pVMemeoryInfo);
            return AURA_E_OUT_OF_MEMORY;
        }

    } else {

        size = (size_t)_PAGE_SIZE;
        pBase = mmap( (Aura_PVoid)vAddress, size, iProt, iFlags, GetFdZero(), 0 );
        if(MAP_FAILED == pBase){
            PERROR("Error: Virtual Memory Commit failed! \
                file:%s. line:%d.\n", __FILE__, __LINE__);
            delete(pVMemeoryInfo);
            return AURA_E_OUT_OF_MEMORY;
        }

    }

    return AURA_NOERROR;
}

AURA_API AuraVirtualMemory_Commit(
        /* [in] */ Aura_Address vAddress)
{
    VMemeoryInfo *pVMemeoryInfo;
    Aura_ECode ec;

    assert(IS_PAGE_ALIGNED(vAddress));

    Lock(s_hVMemeoryInfoLock);
    ForEachDLinkNode(VMemeoryInfo *, pVMemeoryInfo, &s_dVMemeoryInfoList) {
        if(Aura_MemoryMap_Downgrow & pVMemeoryInfo->m_iFlags){
            if((vAddress <= pVMemeoryInfo->m_ulBaseAddr)
                && (vAddress >= pVMemeoryInfo->m_ulBaseAddr - pVMemeoryInfo->m_iTotalSize)){
                ec = CommitAddress(pVMemeoryInfo, vAddress);
                Unlock(s_hVMemeoryInfoLock);
                return ec;
            }
        } else {
            if((vAddress >= pVMemeoryInfo->m_ulBaseAddr)
                && (vAddress <= pVMemeoryInfo->m_ulBaseAddr + pVMemeoryInfo->m_iTotalSize)){
                ec = CommitAddress(pVMemeoryInfo, vAddress);
                Unlock(s_hVMemeoryInfoLock);
                return ec;
            }
        }

    }
    Unlock(s_hVMemeoryInfoLock);

    return AURA_E_DOES_NOT_EXIST;
}

AURA_API AuraVirtualMemory_IORemap(
        /* [in]  */ Aura_Address virtualAddr,
        /* [in]  */ Aura_MemorySize size,
        /* [in]  */ Aura_Address physicalAddr,
        /* [in]  */ Aura_MemoryMapFlags flags,
        /* [in]  */ Aura_MemoryProtection protect,
        /* [out] */ Aura_Address *pVirtualAddr)
{
    assert(NULL != pVirtualAddr);
    return AURA_E_NOT_IMPLEMENTED;
}

AURA_API AuraVirtualMemory_Unmap(
        /* [in]  */ Aura_Address vBaseAddr,
        /* [in]  */ Aura_MemorySize size)
{
    VMemeoryInfo *pVMemeoryInfo;

    Lock(s_hVMemeoryInfoLock);
    ForEachDLinkNode(VMemeoryInfo *, pVMemeoryInfo, &s_dVMemeoryInfoList) {
        if((vBaseAddr == pVMemeoryInfo->m_ulBaseAddr)
            && (size == pVMemeoryInfo->m_iTotalSize)){
            munmap( (Aura_PVoid)pVMemeoryInfo->m_ulBaseAddr,
                (size_t)pVMemeoryInfo->m_iTotalSize);
            pVMemeoryInfo->Detach();
            delete pVMemeoryInfo;
            Unlock(s_hVMemeoryInfoLock);
            return AURA_NOERROR;
        }
    }
    Unlock(s_hVMemeoryInfoLock);

    return AURA_E_INVALID_ARGUMENT;
}

